import
  strformat, os, confutils, algorithm, sequtils

type
  Command = enum
    restart_nodes
    reset_network

  CliConfig = object
    network: string

    validatorsDir {.
      defaultValue: "validators"
      name: "validators-dir" }: string

    case cmd {.command.}: Command
    of restart_nodes:
      discard

    of reset_network:
      secretsDir {.
        defaultValue: "secrets"
        name: "secrets-dir" }: string

      networkDataDir {.
        defaultValue: "data"
        name: "network-data-dir" }: string

      totalUserValidators {.
        defaultValue: 0
        name: "user-validators" }: int

  Node = object
    id: int
    server: string
    container: string

var conf = load CliConfig

var
  serverCount = 6
  instancesCount = 1
  validators = listDirs(conf.validatorsDir).mapIt(splitPath(it)[1])

sort(validators)

proc findOrDefault[K, V](tupleList: openArray[(K, V)], key: K, default: V): V =
  for t in tupleList:
    if t[0] == key:
      return t[1]

  return default

iterator nodes: Node =
  for i in 0 ..< serverCount:
    let
      serverShortName = if i == 0: "master-01" else: &"node-0{i}"
      server = &"{serverShortName}.aws-eu-central-1a.nimbus.test.statusim.net"

    for j in 0 ..< instancesCount:
      yield Node(id: i*instancesCount + j,
                 server: server,
                 container: &"beacon-node-{conf.network}-{j+1}")

iterator validatorAssignments: tuple[node: Node; firstValidator, lastValidator: int] =
  let
    systemValidators = validators.len - conf.totalUserValidators

    defaultValidatorAssignment = proc (nodeIdx: int): int =
      (systemValidators div serverCount) div instancesCount

    customValidatorAssignments = {
      # This is used just to force the correct type of the table
      "default": defaultValidatorAssignment
      ,
      "testnet0": proc (nodeIdx: int): int =
        if nodeidx < 4:
          systemValidators div 4
        else:
          0
      ,
      "testnet1": proc (nodeIdx: int): int =
        if nodeidx < 4:
          systemValidators div 4
        else:
          0
    }

  var nextValidatorIdx = conf.totalUserValidators
  for node in nodes():
    let
      validatorAssignmentFn = customValidatorAssignments.findOrDefault(
        conf.network, defaultValidatorAssignment)
      nodeValidatorCount = validatorAssignmentFn(node.id)

    yield (node,
           nextValidatorIdx,
           nextValidatorIdx + nodeValidatorCount)

    inc nextValidatorIdx, nodeValidatorCount

case conf.cmd
of restart_nodes:
  for n in nodes():
    if n.id mod 2 == 0:
      echo &"echo Pulling container image on {n.server} ..."
      # This will only print one line: "docker.io/statusteam/nimbus_beacon_node:testnet1".
      echo &"ssh {n.server} docker pull -q statusteam/nimbus_beacon_node:{conf.network}"
    echo &"echo Starting container {n.container}@{n.server} ..."
    # docker-compose will rebuild the container if it detects a newer image.
    # Prints: "Recreating beacon-node-testnet1-1 ... done".
    echo &"ssh {n.server} 'cd /docker/{n.container} && docker-compose --compatibility up -d'"

of reset_network:
  for n, firstValidator, lastValidator in validatorAssignments():
    var
      validatorDirs = ""
      secretFiles = ""
      networkDataFiles = conf.networkDataDir & "/{genesis.ssz,bootstrap_nodes.txt}"

    for i in firstValidator ..< lastValidator:
      validatorDirs.add " "
      validatorDirs.add conf.validatorsDir / validators[i]
      secretFiles.add " "
      secretFiles.add conf.secretsDir / validators[i]

    let dockerPath = &"/docker/{n.container}/data/BeaconNode"
    echo &"echo Syncing {lastValidator - firstValidator} keys starting from {firstValidator} to container {n.container}@{n.server} ... && \\"
    echo &"  ssh {n.server} 'sudo rm -rf /tmp/nimbus && mkdir -p /tmp/nimbus/{{net-data,validators,secrets}}' && \\"
    echo &"  rsync -a -zz {networkDataFiles} {n.server}:/tmp/nimbus/net-data/ && \\"
    if validators.len > 0:
      echo &"  rsync -a -zz {validatorDirs} {n.server}:/tmp/nimbus/validators/ && \\"
      echo &"  rsync -a -zz {secretFiles} {n.server}:/tmp/nimbus/secrets/ && \\"

    echo &"  ssh {n.server} 'sudo docker container stop {n.container}; " &
                         &"sudo rm -rf {dockerPath}/{{db,validators,secrets,net-data}}* && " &
                         &"sudo mv /tmp/nimbus/* {dockerPath}/ && " &
                         &"sudo chown dockremap:docker -R {dockerPath}'"

